/**
 * 微信工具包 v3版本
 * 请勿修改此处代码，因为插件更新后此处代码会被覆盖。
 * 作者：VK
 */
const crypto = require("crypto");

class VkWxPayV3 {

	constructor(obj) {
		this.config = {
			appId: obj.appId, // appId
			mchId: obj.mchId, // 商户id
			apiV3key: obj.apiV3key, // api v3密钥
			appCertSn: obj.appCertSn, // 商户API证书」的「证书序列号」
			privateKey: obj.privateKey, //「商户API私钥」
			wxpayPublicCertSn: obj.wxpayPublicCertSn, // 微信支付公钥证书的序列号
			wxpayPublicCertContent: obj.wxpayPublicCertContent, // 微信支付公钥内容
			authType: obj.authType || "WECHATPAY2-SHA256-RSA2048"
		};
	}
	/**
	 * 构建请求签名参数
	 * @param method Http 请求方式
	 * @param url 请求接口 例如/v3/certificates
	 * @param timestamp 获取发起请求时的系统当前时间戳
	 * @param nonceStr 随机字符串
	 * @param body 请求报文主体
	 */
	getSignature(method, nonce_str, timestamp, url, body) {
		let str = method + '\n' + url + '\n' + timestamp + '\n' + nonce_str + '\n';
		if (body && body instanceof Object)
			body = JSON.stringify(body);
		if (body)
			str = str + body + '\n';
		if (method === 'GET')
			str = str + '\n';

		return this.sha256WithRsa(str);
	}
	/**
	 * SHA256withRSA
	 * @param data 待加密字符
	 * @param privatekey 私钥key  key.pem   fs.readFileSync(keyPath)
	 */
	sha256WithRsa(data) {
		if (!this.config.privateKey) {
			throw new Error('缺少秘钥文件');
		}
		return crypto.createSign('RSA-SHA256').update(data).sign(this.config.privateKey, 'base64');
	}
	/**
	 * 获取请求头授权字符串
	 * @param {Object} nonce_str
	 * @param {Object} timestamp
	 * @param {Object} signature
	 */
	getAuthorization(nonce_str, timestamp, signature) {
		let _authorization = 'mchid="' +
			this.config.mchId +
			'",' +
			'nonce_str="' +
			nonce_str +
			'",' +
			'timestamp="' +
			timestamp +
			'",' +
			'serial_no="' +
			this.config.appCertSn +
			'",' +
			'signature="' +
			signature +
			'"';
		return this.config.authType.concat(' ').concat(_authorization);
	}
	/**
	 * 验证签名，提醒：node 取头部信息时需要用小写，例如：req.headers['wechatpay-timestamp']
	 * @param params.timestamp HTTP头Wechatpay-Timestamp 中的应答时间戳
	 * @param params.nonce HTTP头Wechatpay-Nonce 中的应答随机串
	 * @param params.body 应答主体（response Body），需要按照接口返回的顺序进行验签，错误的顺序将导致验签失败。
	 * @param params.serial HTTP头Wechatpay-Serial 证书序列号
	 * @param params.signature HTTP头Wechatpay-Signature 签名
	 * @param params.apiSecret APIv3密钥，如果在 构造器 中有初始化该值(this.key)，则可以不传入。当然传入也可以
	 */
	verifySign(params) {
		let { timestamp, nonce, body, serial, signature, apiSecret } = params;
		let publicKey = this.config.wxpayPublicCertContent;
		let bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
		let data = "".concat(timestamp, "\n").concat(nonce, "\n").concat(bodyStr, "\n");
		return crypto.createVerify('RSA-SHA256').update(data).verify(publicKey, signature, 'base64');
	}
	/**
	 * 回调解密
	 * @param ciphertext  Base64编码后的开启/停用结果数据密文
	 * @param associated_data 附加数据
	 * @param nonce 加密使用的随机串
	 * @param key  APIv3密钥
	 */
	decipherGcm(ciphertext, associated_data, nonce, key) {
		if (!key) key = this.config.apiV3key;
		if (!key) throw new Error('缺少v3密钥key');
		let _ciphertext = Buffer.from(ciphertext, 'base64');
		// 解密 ciphertext字符  AEAD_AES_256_GCM算法
		let authTag = _ciphertext.slice(_ciphertext.length - 16);
		let data = _ciphertext.slice(0, _ciphertext.length - 16);
		let decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
		decipher.setAuthTag(authTag);
		decipher.setAAD(Buffer.from(associated_data));
		let decoded = decipher.update(data, undefined, 'utf8');
		decipher.final();
		try {
			return JSON.parse(decoded);
		} catch (e) {
			return decoded;
		}
	}

	async request(obj = {}) {
		if (Object.prototype.toString.call(obj.content) === "[object object]") obj.content = JSON.stringify(obj
			.content);
		if (typeof obj.dataType === "undefined") obj.dataType = "json";
		// 当返回的是二进制时,需要设置dataType = default
		if (obj.dataType == "default" || obj.dataType === "") delete obj.dataType;
		if (obj.useContent) obj.content = JSON.stringify(obj.data);
		if (!obj.method) obj.method = "POST";
		if (typeof obj.headers === "undefined" && typeof obj.header !== "undefined") {
			obj.headers = obj.header;
		}
		let originalRes = await uniCloud.httpclient.request(obj.url, obj);
		if (!obj.needOriginalRes && originalRes && originalRes.data) {
			return originalRes.data;
		} else {
			return originalRes;
		}
	};

	async wxPayRequest(obj = {}) {
		let {
			method = "GET",
				url,
				data
		} = obj;
		let timestamp = parseInt(Date.now() / 1000);
		let nonce_str = this.random(32);
		let signature = this.getSignature(method, nonce_str, timestamp, url, data);
		let authorization = this.getAuthorization(nonce_str, timestamp, signature);
		let domain = "https://api.mch.weixin.qq.com";
		let res = await this.request({
			url: domain + url,
			method,
			headers: {
				"Content-Type": "application/json",
				"Authorization": authorization
			},
			data
		});
		if (res.message) {
			res.msg = res.message;
			delete res.message;
		}
		return res;
	};

	/**
	 * 产生指定位数的随机数(支持任意字符,默认纯数字)
	 * @params	{Number} length 数据源
	 * @params	{String} str 指定的字符串中随机范围
	 */
	random(length, str) {
		let s = "";
		let list = "0123456789";
		if (str) list = str;
		for (let i = 0; i < length; i++) {
			let code = list[Math.floor(Math.random() * list.length)];
			s += code;
		}
		return s;
	};

	/**
	 * 商家转账到零钱
	 * 详细参数：https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml
	 * @param {String} out_batch_no  商户系统内部的商家批次单号，要求此参数只能由数字、大小写字母组成，在商户系统内部唯一 示例值：plfk2020042013
	 * @param {String} batch_name 该笔批量转账的名称 示例值：2019年1月深圳分部报销单
	 * @param {String} batch_remark 转账说明，UTF8编码，最多允许32个字符 示例值：2019年1月深圳分部报销单
	 * @param {String} total_amount 转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致，否则无法发起转账操作 示例值：4000000
	 * @param {String} total_num 一个转账批次单最多发起三千笔转账。转账总笔数必须与批次内所有明细之和保持一致，否则无法发起转账操作 示例值：200
	 * @param {Array} transfer_detail_list 发起批量转账的明细列表，最多三千笔
	 * transfer_detail_list 参数
	 * @param {String} out_detail_no 商户系统内部区分转账批次单下不同转账明细单的唯一标识，要求此参数只能由数字、大小写字母组成 示例值：x23zy545Bd5436
	 * @param {String} transfer_amount 转账金额单位为分 示例值：200000
	 * @param {String} transfer_remark 单条转账备注（微信用户会收到该备注），UTF8编码，最多允许32个字符 示例值：2020年4月报销
	 * @param {String} openid openid是微信用户在公众号appid下的唯一用户标识（appid不同，则获取到的openid就不同），可用于永久标记一个用户
	 * @param {String} user_name  明细转账金额 >= 2,000元，收款用户姓名必填；
	 */
	async transfer(data={}) {
		let {
			appid,
			out_batch_no,
			batch_name,
			batch_remark,
			total_amount,
			total_num,
			transfer_detail_list
		} = data;
		if (!appid) {
			appid = this.config.appId;
		}
		let res = await this.wxPayRequest({
			url: `/v3/transfer/batches`,
			method: "POST",
			data: {
				appid,
				out_batch_no,
				batch_name,
				batch_remark,
				total_amount,
				total_num,
				transfer_detail_list
			}
		});
		return res;
	}

	/**
	 * 拉取平台证书到 Pay.certificates 中
	 */
	async fetchCertificates(data={}){
		let { apiV3key } = data;
		if (!apiV3key) apiV3key = this.config.apiV3key;
		let res = await this.wxPayRequest({
			url: `/v3/certificates`,
			method: "GET"
		});
		return res;
	}


}

module.exports = VkWxPayV3;
